﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Threading;
using System.Threading;
using System.Threading.Tasks;
using Algoritmia.DataStructures.PriorityQueues;

namespace Demos.Heap
{
    public enum PruebaInsercionesOpcion1
    {
        Azar,
        Intervalo
    } ;

    public enum PruebaInsercionesOpcion2
    {
        Desordenados,
        MayorAMenor,
        MenorAMayor
    } ;

    public delegate void PruebaInsercionesHandler(double tiempo, double tiempoMedio);

    class ViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        private double _velocidadAnimacion;
        public double VelocidadAnimacion
        {
            get { return _velocidadAnimacion; }
            set
            {
                _velocidadAnimacion = value;
                NotifyPropertyChanged("VelocidadAnimacion");
            }
        }

        private string _numeroNodos;
        public string NumeroNodos
        {
            get { return _numeroNodos; }
            set
            {
                _numeroNodos = value;
                NotifyPropertyChanged("NumeroNodos");
            }
        }

        private string _elementoInsertar;
        public string ElementoInsertar
        {
            get { return _elementoInsertar; }
            set
            {
                _elementoInsertar = value;
                NotifyPropertyChanged("ElementoInsertar");
            }
        }

        public ObservableCollection<ElementoVisual> ContenidoArbol { get; set; }
        public ObservableCollection<ElementoVisual> ContenidoLista { get; set; }

        private string _numeroInserciones;
        public string NumeroInserciones
        {
            get { return _numeroInserciones; }
            set
            {
                _numeroInserciones = value;
                NotifyPropertyChanged("NumeroInserciones");
            }
        }

        private bool[] _opciones;
        public bool[] Opciones
        {
            get { return _opciones; }
            set
            {
                _opciones = value;
                NotifyPropertyChanged("Opciones");
            }
        }

        private string _intervaloMin;
        public string IntervaloMin
        {
            get { return _intervaloMin; }
            set
            {
                _intervaloMin = value;
                NotifyPropertyChanged("IntervaloMin");
            }
        }

        private string _intervaloMax;
        public string IntervaloMax
        {
            get { return _intervaloMax; }
            set
            {
                _intervaloMax = value;
                NotifyPropertyChanged("IntervaloMax");
            }
        }

        private string _tiempoAcumuladoInserciones;
        public string TiempoAcumuladoInserciones
        {
            get { return _tiempoAcumuladoInserciones; }
            set
            {
                _tiempoAcumuladoInserciones = value;
                NotifyPropertyChanged("TiempoAcumuladoInserciones");
            }
        }

        private string _tiempoMedioInserciones;
        public string TiempoMedioInserciones
        {
            get { return _tiempoMedioInserciones; }
            set
            {
                _tiempoMedioInserciones = value;
                NotifyPropertyChanged("TiempoMedioInserciones");
            }
        }

        private string _tiempoAcumuladoExtracciones;
        public string TiempoAcumuladoExtracciones
        {
            get { return _tiempoAcumuladoExtracciones; }
            set
            {
                _tiempoAcumuladoExtracciones = value;
                NotifyPropertyChanged("TiempoAcumuladoExtracciones");
            }
        }

        private string _tiempoMedioExtracciones;
        public string TiempoMedioExtracciones
        {
            get { return _tiempoMedioExtracciones; }
            set
            {
                _tiempoMedioExtracciones = value;
                NotifyPropertyChanged("TiempoMedioExtracciones");
            }
        }

        private Visibility _visibilidadControles;
        public Visibility VisibilidadControles
        {
            get { return _visibilidadControles; }
            set
            {
                _visibilidadControles = value;
                NotifyPropertyChanged("VisibilidadControles");
            }
        }

        private Visibility _visibilidadControlesAnimacion;
        public Visibility VisibilidadControlesAnimacion
        {
            get { return _visibilidadControlesAnimacion; }
            set
            {
                _visibilidadControlesAnimacion = value;
                NotifyPropertyChanged("VisibilidadControlesAnimacion");
            }
        }

        private int _pestañaSeleccionada;
        public int PestañaSeleccionada
        {
            get { return _pestañaSeleccionada; }
            set
            {
                _pestañaSeleccionada = value;
                if (value == 0)
                {
                    VisibilidadControles = Visibility.Visible;
                    VisibilidadControlesAnimacion = Visibility.Visible;
                }
                else if (value == 1)
                {
                    VisibilidadControles = Visibility.Visible;
                    VisibilidadControlesAnimacion = Visibility.Hidden;
                }
                else
                {
                    VisibilidadControles = Visibility.Hidden;
                }
                NotifyPropertyChanged("PestañaSeleccionada");
            }
        }

        public double MaxX { get; set; }
        public double MaxY { get; set; }

        public ICommand GenerarHeapCommand { get; private set; }
        public ICommand InsertarElementoCommand { get; private set; }
        public ICommand BorrarRaizCommand { get; private set; }
        public ICommand StepCommand { get; private set; }
        public ICommand ComenzarInsercionesCommand { get; private set; }
        public ICommand ComenzarExtraccionesCommand { get; private set; }

        DataStructures.ObservableMaxHeap _model;
        private Heap<int> _heapExperimentos;
        readonly Func<string, int> _disparaCambio;
        public event PruebaInsercionesHandler PruebaInsercionesFinalizada;
        public event PruebaInsercionesHandler PruebaExtraccionesFinalizada;
        List<string> _movimientos = new List<string>();
        private bool _step;


        public ViewModel()
        {
            _disparaCambio = Dibujar;
            _model = new DataStructures.ObservableMaxHeap(_disparaCambio);

            VelocidadAnimacion = 200;
            NumeroInserciones = "1000";
            Opciones = new bool[5];//azar, intervalo, desordenados, ordenados <, ordenados >
            Opciones[0] = true;
            Opciones[2] = true;
            _step = false;
            
            ContenidoArbol = new ObservableCollection<ElementoVisual>();
            ContenidoLista = new ObservableCollection<ElementoVisual>();
            _heapExperimentos = new Heap<int>();

            GenerarHeapCommand = new RelayCommand(GenerarHeap, () => !string.IsNullOrEmpty(NumeroNodos));
            InsertarElementoCommand = new RelayCommand(InsertarElemento, () => !string.IsNullOrEmpty(ElementoInsertar));
            BorrarRaizCommand = new RelayCommand(BorrarRaiz, () => ContenidoArbol.Count > 0);
            StepCommand = new RelayCommand(Step, () => _step);
            ComenzarInsercionesCommand = new RelayCommand(ComenzarInserciones, PuedoComenzarInserciones);
            ComenzarExtraccionesCommand = new RelayCommand(ComenzarExtracciones, () => _heapExperimentos.Count > 0);
        }


        private void ComenzarExtracciones()
        {
            PruebaExtraccionesFinalizada += PruebaExtraccionesPublicador;
            PruebaExtracciones();
        }

        void PruebaExtraccionesPublicador(double tiempo, double tiempoMedio)
        {
            TiempoAcumuladoExtracciones = string.Format("{0} milisegundos", tiempo);
            TiempoMedioExtracciones = string.Format("{0} milisegundos", tiempoMedio);
        }

        private bool PuedoComenzarInserciones()
        {
            if (!Opciones[1])
            {
                return !string.IsNullOrEmpty(NumeroInserciones);
            }
            return !string.IsNullOrEmpty(NumeroInserciones) && !string.IsNullOrEmpty(IntervaloMin) &&
                !string.IsNullOrEmpty(IntervaloMax);

        }

        private void ComenzarInserciones()
        {
            var opcion1 = PruebaInsercionesOpcion1.Azar;
            var opcion2 = PruebaInsercionesOpcion2.Desordenados;
            int numInserciones = 0, desde = 0, hasta = 0;

            if (Opciones[1])
            {
                opcion1 = PruebaInsercionesOpcion1.Intervalo;
            }

            if (Opciones[3])
            {
                opcion2 = PruebaInsercionesOpcion2.MenorAMayor;
            }
            else if (Opciones[4])
            {
                opcion2 = PruebaInsercionesOpcion2.MayorAMenor;
            }

            try
            {
                numInserciones = int.Parse(NumeroInserciones);
                if (opcion1 == PruebaInsercionesOpcion1.Intervalo)
                {
                    desde = int.Parse(IntervaloMin);
                    hasta = int.Parse(IntervaloMax);
                }
                else
                {
                    desde = 0;
                    hasta = 0;
                }
            }
            catch (FormatException)
            {
                MessageBox.Show("Formato Incorrecto", "Error", MessageBoxButton.OK, MessageBoxImage.Exclamation);
            }

            PruebaInsercionesFinalizada += PruebaInsercionesPublicador;
            PruebaInserciones(numInserciones, opcion1, opcion2, desde, hasta);
        }

        void PruebaInsercionesPublicador(double tiempo, double tiempoMedio)
        {
            TiempoAcumuladoInserciones = string.Format("{0} milisegundos", tiempo);
            TiempoMedioInserciones = string.Format("{0} milisegundos", tiempoMedio);
        }

        private void Step()
        {
            bool quedanMovimientos = Animar();
            if (quedanMovimientos == false) _step = false;
        }

        private void BorrarRaiz()
        {
            try
            {
                _model.ExtractOptimal();
                _step = true;
            }
            catch (Exception)
            { }
        }

        private void InsertarElemento()
        {
            try
            {
                _model.Add(int.Parse(ElementoInsertar));
                _step = true;
            }
            catch (FormatException)
            {
                MessageBox.Show("Indica el número a insertar");
            }
        }

        private void GenerarHeap()
        {
            try
            {
                var num = int.Parse(NumeroNodos);
                _model = new DataStructures.ObservableMaxHeap(_disparaCambio);
                var r = new Random(DateTime.Now.Millisecond);
                for (var i = 0; i < num; i++)
                {
                    var aleat = r.Next(1, 99);
                    _model.Add(aleat);
                }
            }
            catch (FormatException)
            {
                MessageBox.Show("Indica el número de elementos");
            }
        }

        public int Dibujar(string info)
        {
            String[] args = info.Split(' ');
            if (args[0] == "Swap")
            {
                _movimientos.Add(args[1] + " " + args[2]);
            }
            else if (args[0] == "Add")
            {
                DibujaPizarra();
            }

            else if (args[0] == "WindowResize")
            {
                DibujaPizarra();
            }

            DibujaLista();
            return 0;
        }

        private void DibujaPizarra()
        {
            ContenidoArbol.Clear();
            var totalFilas = (int)Math.Floor(Math.Log(_model.Count, 2) + 1);
            var tamFila = MaxY / (totalFilas);
            var tamColumna = new List<double>();
            for (var fila = 1; fila < totalFilas + 3; fila++)
            {
                var columnas = Math.Pow(2, fila) / 2;
                tamColumna.Add(MaxX / columnas);
            }
            var x = MaxX / 2;
            double y = 0;
            var nodos = 0;
            var filas = 1;
            foreach (var objeto in _model)
            {
                //Pintar nodo
                nodos += 1;
                ContenidoArbol.Add(new Bola(x,y, objeto.ToString()));

                //Pintar arco al padre
                try
                {
                    if (nodos != 1)
                    {
                        var arco = new Line();
                        if (nodos % 2 == 0)
                        {
                            arco.X1 = tamColumna[filas] + 10;
                        }
                        else
                        {
                            arco.X1 = -tamColumna[filas] + 10;
                        }
                        arco.X2 = 10;
                        arco.Y1 = -tamFila + 25;
                        arco.Y2 = 1;
                        ContenidoArbol.Add(new Flecha(arco.X1, arco.Y1, arco.X2, arco.Y2, x, y));
                    }
                }
                catch (Exception) { }

                //Salto de linea?
                if (nodos >= Math.Pow(2, filas) - 1)
                {
                    filas += 1;
                    y += tamFila;
                    x = tamColumna[filas - 1] / 2;
                }
                else
                {
                    x += tamColumna[filas - 1];
                }
            }
        }

        private void DibujaLista()
        {
            ContenidoLista.Clear();

            foreach (int numero in _model)
            {
                ContenidoLista.Add(new Caja(numero.ToString()));
            }
        }

        private bool Animar()
        {
            if (_movimientos.Count == 0)
            {
                return false;
            }

            var duracionMilisegundos = VelocidadAnimacion;
            String[] mov = _movimientos[0].Split(' ');
            _movimientos.RemoveAt(0);

            //posicion de los elementos a intercambiar
            var b1Sa = int.Parse(mov[0]);
            var b2Sa = int.Parse(mov[1]);

            //contando los arcos
            var b1 = b1Sa == 0 ? 0 : b1Sa * 2 - 1;
            var b2 = b2Sa == 0 ? 0 : b2Sa * 2 - 1;


            var b1Left = ContenidoArbol[b1].X;
            var b1Top = ContenidoArbol[b1].Y;
            var b2Left = ContenidoArbol[b2].X;
            var b2Top = ContenidoArbol[b2].Y;

            var duration1 = new Duration(TimeSpan.FromMilliseconds(duracionMilisegundos));
            var duration2 = new Duration(TimeSpan.FromMilliseconds(duracionMilisegundos));

            //Animacion b1
            var sb1 = new Storyboard();
            var myDoubleAnimation1 = new DoubleAnimation();
            var myDoubleAnimation2 = new DoubleAnimation();

            myDoubleAnimation1.Duration = duration1;
            myDoubleAnimation2.Duration = duration2;
            sb1.Duration = duration1;
            sb1.Children.Add(myDoubleAnimation1);
            sb1.Children.Add(myDoubleAnimation2);
            Storyboard.SetTarget(myDoubleAnimation1, ContenidoArbol[b1]);
            Storyboard.SetTarget(myDoubleAnimation2, ContenidoArbol[b1]);
            Storyboard.SetTargetProperty(myDoubleAnimation1, new PropertyPath("(X)"));
            Storyboard.SetTargetProperty(myDoubleAnimation2, new PropertyPath("(Y)"));
            myDoubleAnimation1.From = b1Left;
            myDoubleAnimation1.To = b2Left;
            myDoubleAnimation2.From = b1Top;
            myDoubleAnimation2.To = b2Top;

            //Animacion b2
            var sb2 = new Storyboard();
            myDoubleAnimation1 = new DoubleAnimation();
            myDoubleAnimation2 = new DoubleAnimation();

            myDoubleAnimation1.Duration = duration1;
            myDoubleAnimation2.Duration = duration2;
            sb1.Duration = duration1;
            sb1.Children.Add(myDoubleAnimation1);
            sb1.Children.Add(myDoubleAnimation2);
            Storyboard.SetTarget(myDoubleAnimation1, ContenidoArbol[b2]);
            Storyboard.SetTarget(myDoubleAnimation2, ContenidoArbol[b2]);
            Storyboard.SetTargetProperty(myDoubleAnimation1, new PropertyPath("(X)"));
            Storyboard.SetTargetProperty(myDoubleAnimation2, new PropertyPath("(Y)"));
            myDoubleAnimation1.From = b2Left;
            myDoubleAnimation1.To = b1Left;
            myDoubleAnimation2.From = b2Top;
            myDoubleAnimation2.To = b1Top;

            sb1.Completed += Sb1Completed;

            sb1.Begin();
            sb2.Begin();

            return ActualizarIndicesMovimientos(b1Sa, b2Sa);
        }

        private bool ActualizarIndicesMovimientos(int b1Sa, int b2Sa)
        {
            var movimientosActualizados = new List<string>();
            for (var i = 0; i < _movimientos.Count; i++)
            {
                String[] moviTemp = _movimientos[i].Split(' ');
                var b1Temp = int.Parse(moviTemp[0]);
                var b2Temp = int.Parse(moviTemp[1]);
                int b1Final, b2Final;

                if (b1Temp == b1Sa)
                    b1Final = b2Sa;
                else if (b1Temp == b2Sa)
                    b1Final = b1Sa;
                else
                    b1Final = b1Temp;

                if (b2Temp == b1Sa)
                    b2Final = b2Sa;
                else if (b2Temp == b2Sa)
                    b2Final = b1Sa;
                else
                    b2Final = b2Temp;

                movimientosActualizados.Add(b1Final + " " + b2Final);
            }
            _movimientos = movimientosActualizados;
            return _movimientos.Count != 0;
        }

        void Sb1Completed(object sender, EventArgs e)
        {
            if (_movimientos.Count == 0)
                DibujaPizarra();
        }

        private void PruebaInserciones(int numInserciones, PruebaInsercionesOpcion1 op1, PruebaInsercionesOpcion2 op2, int intervalo1, int intervalo2)
        {
            _heapExperimentos = new Heap<int>();
            var valores = CrearValores(op1, numInserciones, intervalo1, intervalo2);
            valores = OrdenarValores(op2, valores);
            EjecutarPruebaInserciones(numInserciones, valores);
        }

        private List<int> CrearValores(PruebaInsercionesOpcion1 op1, int numInserciones, int intervalo1, int intervalo2)
        {
            var valores = new List<int>();
            var r = new Random(DateTime.Now.Millisecond);

            if (op1 == PruebaInsercionesOpcion1.Azar)
            {
                for (int i = 0; i < numInserciones; i++)
                {
                    valores.Add(r.Next());
                }
            }
            else if (op1 == PruebaInsercionesOpcion1.Intervalo)
            {
                for (int i = 0; i < numInserciones; i++)
                {
                    valores.Add(r.Next(intervalo1, intervalo2));
                }
            }
            return valores;
        }

        private List<int> OrdenarValores(PruebaInsercionesOpcion2 op2, List<int> valores)
        {
            if (op2 != PruebaInsercionesOpcion2.Desordenados)
            {
                valores.Sort();
            }
            if (op2 == PruebaInsercionesOpcion2.MayorAMenor)
            {
                valores.Reverse();
            }
            return valores;
        }

        private void EjecutarPruebaInserciones(int numInserciones, IList<int> valores)
        {
            var mainDispatcher = Dispatcher.CurrentDispatcher;
            var prueba = new Task(() =>
            {
                DateTime tiempo1 = DateTime.Now;
                for (int i = 0; i < numInserciones; i++)
                {
                    _heapExperimentos.Add(valores[i]);
                }
                DateTime tiempo2 = DateTime.Now;

                var tiempoAcumulado = (tiempo2 - tiempo1).TotalMilliseconds;

                mainDispatcher.BeginInvoke(DispatcherPriority.Normal, new ThreadStart(
                    () => PruebaInsercionesFinalizada(tiempoAcumulado, tiempoAcumulado / numInserciones)));
            });
            prueba.Start();
        }

        private void PruebaExtracciones()
        {
            Dispatcher mainDispatcher = Dispatcher.CurrentDispatcher;

            var prueba = new Task(() =>
            {
                var numElementos = _heapExperimentos.Count;
                var tiempo1 = DateTime.Now;
                while (_heapExperimentos.Count > 0)
                {
                    _heapExperimentos.ExtractOptimal();
                }
                var tiempo2 = DateTime.Now;

                var tiempoAcumulado = (tiempo2 - tiempo1).TotalMilliseconds;

                mainDispatcher.BeginInvoke(DispatcherPriority.Normal, new ThreadStart(
                    () => PruebaExtraccionesFinalizada(tiempoAcumulado, tiempoAcumulado / numElementos)));
            });
            prueba.Start();
        }
    }
}
